﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Http.Controllers;

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;

using ProjectBase.Core;
using ProjectBase.Core.Service;
using ProjectBase.Mvc;

namespace $safeprojectname$
{
    /// <summary>
    /// Adapter.
    /// Entity Converter is responsible for object creation (CreateNew, GetById)
    /// and Injecting its inner complex properties 
    /// </summary>
    public class EntityConverter<TEntity, TID>
        : CustomCreationConverter<TEntity>
        , IMessagesHolder
        , IService
        where TEntity : class, IPersistentObjectWithTypedId<TID>
    {
        #region Factory
        public override TEntity Create(Type objectType)
        {
            if (ExistingEntity.Is())
            {
                return ExistingEntity;
            }
            // Add
            if (Context.Is() && Context.Request.Method.Method == "POST")
            {
                var readFacade = Factory.CreateInstance<IReadFacade<TEntity, TID>>();
                return readFacade.CreateNew();
            }

            // Update
            TID id;
            if (TryGetCurrentActionID(out id))
            {
                var readFacade = Factory.CreateInstance<IReadFacade<TEntity, TID>>();
                return readFacade.GetById(id);
            }

            PublishingManager.Publish(this, Severity.Error, "Cannot process Update. The passed ID: [{0}] has wrong format"
                , Context.RouteData.Values["action"] ?? "null");

            return default(TEntity);
        }
        #endregion Factory

        #region Action id

        static readonly IList<string> PutGet = new[] { "PUT", "GET" };
        public virtual bool TryGetCurrentActionID(out TID id)
        {
            id = default(TID);

            if (Context.IsNull())
            {
                return false;
            }

            var values = Context.RouteData.Values;
            var method = Context.Request.Method.Method;

            var isDetailOrUpdate = values.ContainsKey("controller")
                                   && values.ContainsKey("id")
                                   && PutGet.Contains(method)
                ;

            if (!isDetailOrUpdate)
            {
                return false;
            }

            var idString = (Context.RouteData.Values["id"] ?? string.Empty).ToString();


            if (TryGetId(idString, out id))
            {
                return true;
            }
            return false;
        }

        #endregion Action id

        #region Binding

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            try
            {
                // keep the json as the queryable JObject
                var jObject = JToken.ReadFrom(reader) as JObject;

                // get the default result
                var result = base.ReadJson(jObject.CreateReader(), objectType, existingValue, serializer);

                BindPersitentProperties(result, jObject);
                return result;
            }
            catch (JsonReaderException ex)
            {
                PublishingManager.Publish(this, "JSON is not well formatted ({0})".FormatWith(ex.Message), ex);
            }
            catch (FormatException ex)
            {
                PublishingManager.Publish(this, "JSON is not well formatted ({0})".FormatWith(ex.Message), ex);
            }
            return null;
        }

        protected virtual void BindPersitentProperties(object toBeBounded, JObject jObject)
        {
            var persitent = toBeBounded as IPersistentObjectWithTypedId<TID>;
            if (persitent.IsNull())
            {
                return;
            }

            var properties = jObject
                .Descendants()
                .Where(p => p is JProperty)
                .Cast<JProperty>()
                .ToList()
                ;

            foreach (JProperty property in properties)
            {
                BindPersitentProperty(persitent, property);
            }
        }
        protected virtual void BindPersitentProperty(IPersistentObjectWithTypedId<TID> persistent, JProperty jProperty)
        {
            // Assume 'Parent' property to be bounded
            // Let's try from a 'ParentId' get the value of ID and assign the existing instance

            var persitentName = jProperty.Name;
            if (persitentName.EndsWith("Id", StringComparison.InvariantCultureIgnoreCase))
            {
                persitentName = persitentName.Substring(0, persitentName.Length - 2);
            }

            var propertyInfo = persistent.GetType().GetProperty(persitentName);

            var attribute = Attributes
                .FirstOrDefault(a => a.Name.IsEqual(persitentName));

            // Is there such Persistent property?
            var canSetProperty = attribute.Is()
                                && attribute.CanEdit
                                 && attribute.Type.IsEquivalentTo(propertyInfo.PropertyType)
                                 && propertyInfo.Is()
                                 && propertyInfo.CanWrite
                                 && propertyInfo.PropertyType.HasType<IPersistentObjectWithTypedId<int>>()
                ;

            if (!canSetProperty)
            {
                return;
            }

            // explicit set to NULL
            var isNull = jProperty.Value.Value<int?>().IsNull();
            if (isNull)
            {
                propertyInfo.SetValue(persistent, null, null);
                return;
            }

            // Can parse the ID value from 'ParentId
            TID id = jProperty.TryGetValue<TID>();

            // ReadFacade for a Persistent property
            var facade = propertyInfo.PropertyType.GetFacade<TID>();
            var entity = facade.GetById(id);

            if (entity.IsNull())
            {
                PublishingManager.Publish(this, Severity.Error, "Failed to bind property '{0}' of type '{1}'. Not found item with passed ID [{2}] (see Json parameter: '{3}')", persitentName, propertyInfo.PropertyType.Name, jProperty.Value, jProperty.Name);
                return;
            }

            // bounded value
            propertyInfo.SetValue(persistent, entity, null);
        }

        protected virtual bool TryGetId(object objectValue, out TID id)
        {
            var value = objectValue.ToString();

            if (typeof(TID).IsEquivalentTo(typeof(string)))
            {
                id = (TID)(object)value;
                return true;
            }
            ValueType valueType;
            if (value.TryParse(CultureInfo.InvariantCulture, typeof(TID), out valueType))
            {
                id = (TID)(object)valueType;
                return true;
            }


            id = default(TID);
            return false;
        }

        #endregion Binding

        #region properties
        
        //public virtual IReadFacade<TEntity, TID> ReadFacade { get; set; }
        public virtual HttpControllerContext Context { get; set; }
        public virtual TEntity ExistingEntity { protected get; set; }
        #endregion properties

        #region Message
        public virtual Messages Messages { get; set; }
        public virtual IList<MDAttribute> Attributes { get; set; }
        #endregion Message
    }
}